package com.element.oauth2.server.global.converter;

import com.common.core.exception.code.SecurityErrorCode;
import com.element.oauth2.constant.SecurityParams;
import com.element.oauth2.exception.ClientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public class GlobalClientAuthenticationConverter implements AuthenticationConverter {

    @Override
    public Authentication convert(HttpServletRequest request) {
        MultiValueMap<String, String> parameters = getParameters(request);
        if (CollectionUtils.isEmpty(parameters)) {
            throw ClientAuthenticationException.ERROR(SecurityErrorCode.P_ERROR_PARAM_IS_NULL);
        }
        String clientId = parameters.getFirst(SecurityParams.CLIENT_ID);
        if (!StringUtils.hasText(clientId)) {
            throw ClientAuthenticationException.CLIENT_ERROR("客户端参数不存在");
        } else if (parameters.get(SecurityParams.CLIENT_ID).size() != 1) {
            throw ClientAuthenticationException.CLIENT_ERROR("invalid_client_request");
        } else {
            String clientSecret = parameters.getFirst(SecurityParams.CLIENT_SECRET);
            if (!StringUtils.hasText(clientSecret)) {
                throw ClientAuthenticationException.CLIENT_ERROR("客户端参数不存在");
            } else if (parameters.get(SecurityParams.CLIENT_SECRET).size() != 1) {
                throw ClientAuthenticationException.CLIENT_ERROR("invalid_client_request");
            } else {
                Map<String, Object> additionalParameters = getParametersIfMatchesAuthorizationCodeGrantRequest(request);
                return new OAuth2ClientAuthenticationToken(clientId, ClientAuthenticationMethod.CLIENT_SECRET_POST, clientSecret, additionalParameters);
            }
        }
    }

    private MultiValueMap<String, String> getParameters(HttpServletRequest request) {
        Map<String, String[]> parameterMap = request.getParameterMap();
        MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>(parameterMap.size());
        parameterMap.forEach((key, values) -> {
            for (String value : values) {
                parameters.add(key, value);
            }
        });
        return parameters;
    }

    private Map<String, Object> getParametersIfMatchesAuthorizationCodeGrantRequest(HttpServletRequest request) {
        if (!matchesAuthorizationCodeGrantRequest(request)) {
            return Collections.emptyMap();
        } else {
            MultiValueMap<String, String> multiValueParameters = getParameters(request);
            multiValueParameters.remove(SecurityParams.CLIENT_ID);
            multiValueParameters.remove(SecurityParams.CLIENT_SECRET);
            Map<String, Object> parameters = new HashMap<>();
            multiValueParameters.forEach((key, value) -> parameters.put(key, value.size() == 1 ? value.get(0) : value.toArray(new String[0])));
            return parameters;
        }
    }

    private boolean matchesAuthorizationCodeGrantRequest(HttpServletRequest request) {
        return AuthorizationGrantType.AUTHORIZATION_CODE.getValue().equals(request.getParameter(SecurityParams.GRANT_TYPE)) && request.getParameter(SecurityParams.RESPONSE_TYPE_CODE) != null;
    }
}